package peshcurdmitry.ftp.server.sockets;


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

import java.net.Socket;


import java.util.NoSuchElementException;
import java.util.StringTokenizer;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import peshcurdmitry.ftp.server.Server;
import peshcurdmitry.ftp.server.exeptions.CommandException;
import peshcurdmitry.ftp.server.logger.Logger;


/**
 * This is the server protocol interpreter. The main Server object creates one
 * of these for each user connection to interpret user commands.
 *
 * This class run as a thread.
 */
public class ServerPI implements Runnable
{
	/**
	 * The client we are talking to.
	 */
	private Socket clientSocket;

	/**
	 * A Reader for reading from the client socket.
	 */
	private BufferedReader reader;

	/**
	 * A Writer for writing to the client socket.
	 */
	private PrintWriter writer;

//	private ServerDTP dtp;  // it will be the data transfer protocol 

	/**
	 * Reflection is used to invoke a command handler for a given string
	 * command
	 */
	private Class commandHandlerArgTypes[] = { String.class, StringTokenizer.class };

	/**
	 * Stores the username of the user.
	 */
	private String username;

	/**
	 * Stores the password of the user.
	 */
	private String password;

	/**
	 * Creates a server protocol interpreter for the specified client
	 * socket.
	 */
	public ServerPI(Socket clientSocket)
		throws IOException
		{
		this.clientSocket = clientSocket;
		reader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
		writer = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()), true);
//		dtp = new ServerDTP(this);
		}

	/**
	 * Runs the protocol interpreter for a client.
	 */
	public void run()
		{
		try
			{
				clientLoop(); // The actual interpreter loop.
			}
		catch (Exception e)
			{
				e.printStackTrace();
				Logger.log(Logger.LOG_EMERG, "clientLoop failed: " + e);
			}
		finally
			{
			try
				{
					clientSocket.close();
				}
			catch (Exception e)
				{
					e.printStackTrace();
				}
			}
		}

	/**
	 * The loops on input from the client socket, reading each command and
	 * using the reflection API to run command method
	 */
	private void clientLoop()
		throws Exception
		{
		reply(220, "localhost FTP server (" + Server.VERSION + ") ready.");
		String line = null;
//		System.out.println(reader.readLine());
		while ((line = reader.readLine()) != null)
			{
			StringTokenizer st = new StringTokenizer(line);
			String command = st.nextToken().toLowerCase();

			if ("PASS".equalsIgnoreCase(command))
				Logger.log(Logger.LOG_INFO, "PASS ********");
			else
				Logger.log(Logger.LOG_INFO, line);

			Object args[] = { line, st };
			try
				{
				Method commandHandler = getClass().getMethod("handle_" + command, commandHandlerArgTypes);
				int code = ((Integer)commandHandler.invoke(this, args)).intValue();
				if (code == 221)
					return;
				}
			catch (InvocationTargetException e)
				{
				try
					{
					throw (Exception)e.getTargetException();
					}
				catch (CommandException ce)
					{
					reply(ce.getCode(), ce.getText());
					}
				catch (NoSuchElementException e1)
					{
					reply(500, "'" + line + "': command not understood.");
					}
				}
			catch (NoSuchMethodException e)
				{
				reply(500, "'" + line + "': command not understood.");
				}
			catch (Exception e)
				{
				Logger.log(Logger.LOG_ERR, "Exception invoking " + command + " command handler: " + e);
				e.printStackTrace();
				}
			}
		}

	/**
	 * Command handler for the USER command.
	 */
	public int handle_user(String line, StringTokenizer st)
		throws CommandException
		{
		username = st.nextToken();
		return reply(331, "Password required for " + username + ".");
		}

	/**
	 * Command handler for the PASS command. A USER command must have been
	 * read first.
	 */
	public int handle_pass(String line, StringTokenizer st)
		throws CommandException
		{
		if (username == null)
			throw new CommandException(503, "Login with USER first.");

		String password = null;
		if (st.hasMoreTokens())
			password = st.nextToken();
		else
			password = "";
		this.password = password;
		return reply(230, "User " + username + " logged in.");
		}


	int reply(int code, String text)
		{
		writer.println("REPLY:" +code + " " + text);
		return code;
		}

	void checkLogin()
		throws CommandException
		{
		if (password == null)
			throw new CommandException(530, "Please login with USER and PASS.");
		}
}
